#include "Components/AutoShape/AutoShapeConfigSphere.h"

#include "Components/AutoShape/AutoShapeComponent.h"
#include "PhysicsEngine/BodySetup.h"
#include "PhysicsEngine/SphereElem.h"

template <EShapeBodySetupHelper UpdateBodySetupAction, typename BodySetupType>
bool InvalidateOrUpdateSphereBodySetup(BodySetupType& ShapeBodySetup, bool bUseArchetypeBodySetup, float SphereRadius)
{
	check((bUseArchetypeBodySetup && UpdateBodySetupAction == EShapeBodySetupHelper::InvalidateSharingIfStale) || (!bUseArchetypeBodySetup && UpdateBodySetupAction == EShapeBodySetupHelper::UpdateBodySetup));
	check(ShapeBodySetup->AggGeom.SphereElems.Num() == 1);
	FKSphereElem* SphereElem = ShapeBodySetup->AggGeom.SphereElems.GetData();

	// check for mal formed values
	float Radius = SphereRadius;
	if (Radius < UE_KINDA_SMALL_NUMBER)
	{
		Radius = 0.1f;
	}

	if (UpdateBodySetupAction == EShapeBodySetupHelper::UpdateBodySetup)
	{
		// now set the PhysX data values
		SphereElem->Center = FVector::ZeroVector;
		SphereElem->Radius = Radius;
	}
	else
	{
		if (SphereElem->Radius != Radius)
		{
			ShapeBodySetup = nullptr;
			bUseArchetypeBodySetup = false;
		}
	}

	return bUseArchetypeBodySetup;
}

FPrimitiveSceneProxy* UAutoShapeConfigSphere::CreateSceneProxy(UAutoShapeComponent* Owner)
{
	return Owner ? new FKSphereSceneProxy(Owner, this) : Super::CreateSceneProxy(Owner);
}

bool UAutoShapeConfigSphere::IsZeroExtent(const UAutoShapeComponent* Owner) const
{
	return SphereRadius == 0.f;
}

struct FCollisionShape UAutoShapeConfigSphere::GetCollisionShape(const UAutoShapeComponent* Owner, float Inflation) const
{
	const float Radius = FMath::Max(0.f, GetScaledSphereRadius(Owner) + Inflation);
	return FCollisionShape::MakeSphere(Radius);
}

bool UAutoShapeConfigSphere::AreSymmetricRotations(const UAutoShapeComponent* Owner, const FQuat& A, const FQuat& B, const FVector& Scale3D) const
{
	// All rotations are equal when scale is uniform.
	// Not detecting rotations around non-uniform scale.
	return Scale3D.GetAbs().AllComponentsEqual() || A.Equals(B);
}

FBoxSphereBounds UAutoShapeConfigSphere::CalcBounds(const UAutoShapeComponent* Owner, const FTransform& LocalToWorld) const
{
	return FBoxSphereBounds(FVector::ZeroVector, FVector(SphereRadius), SphereRadius).TransformBy(LocalToWorld);
}

void UAutoShapeConfigSphere::CalcBoundingCylinder(const UAutoShapeComponent* Owner, float& CylinderRadius, float& CylinderHalfHeight) const
{
	if (Owner)
	{
		CylinderRadius = SphereRadius * Owner->GetComponentTransform().GetMaximumAxisScale();
		CylinderHalfHeight = CylinderRadius;
	}
	else
	{
		Super::CalcBoundingCylinder(Owner, CylinderRadius, CylinderHalfHeight);
	}
}

void UAutoShapeConfigSphere::UpdateBodySetup(UAutoShapeComponent* Owner)
{
	if (Owner)
	{
		if (Owner->ShapeBodySetup && Owner->ShapeBodySetup->AggGeom.SphereElems.Num() != 1)
		{
			Owner->ShapeBodySetup = nullptr;
			Owner->SetbUseArchetypeBodySetup(false);
		}

		if (Owner->InPrepareSharedBodySetup())
		{
			Owner->SetbUseArchetypeBodySetup(InvalidateOrUpdateSphereBodySetup<EShapeBodySetupHelper::InvalidateSharingIfStale>(Owner->ShapeBodySetup, Owner->GetbUseArchetypeBodySetup(), SphereRadius));
		}

		Owner->InCreateShapeBodySetupIfNeeded<FKSphereElem>();

		if (!Owner->GetbUseArchetypeBodySetup())
		{
			InvalidateOrUpdateSphereBodySetup<EShapeBodySetupHelper::UpdateBodySetup>(Owner->ShapeBodySetup, Owner->GetbUseArchetypeBodySetup(), SphereRadius);
		}
	}
	else
	{
		Super::UpdateBodySetup(Owner);
	}
}

void UAutoShapeConfigSphere::SetSphereRadius(UAutoShapeComponent* Owner, float InSphereRadius, bool bUpdateOverlaps)
{
	if (Owner)
	{
		SphereRadius = InSphereRadius;
		Owner->UpdateBounds();
		Owner->UpdateBodySetup();
		Owner->MarkRenderStateDirty();

		if (Owner->IsPhysicsStateCreated())
		{
			// Update physics engine collision shapes
			Owner->BodyInstance.UpdateBodyScale(Owner->GetComponentTransform().GetScale3D(), true);

			if (bUpdateOverlaps && Owner->IsCollisionEnabled() && Owner->GetOwner())
			{
				Owner->UpdateOverlaps();
			}
		}
	}
	else
	{
		Super::UpdateBodySetup(Owner);
	}
}
